﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Windows.Forms;

using Noris.Schedule.Support.Services;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Core;
using System.Data;
using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.DataFace;
using System.Threading;

namespace Noris.Schedule.Support
{
	#region CLASS ConnectProfile : jednotlivá položka profilu připojení k databázi
	/// <summary>
	/// Jeden profil připojení k databázi (ID, jméno, ConnectString)
	/// </summary>
	public class ConnectProfile : IRecord
	{
		#region KONSTRUKCE
		/// <summary>
		/// Konstruktor pro prázdný objekt
		/// </summary>
		/// <param name="profileId"></param>
		public ConnectProfile(int profileId)
		{
			this._ProfileID = profileId;
			this._ProfileName = String.Empty;
		}
		/// <summary>
		/// Konstruktor pro objekt naplněný daty
		/// </summary>
		/// <param name="profileId"></param>
		/// <param name="profileName"></param>
		/// <param name="dataSource"></param>
		/// <param name="initialCatalog"></param>
		/// <param name="userID"></param>
		/// <param name="password"></param>
		/// <param name="passwordCrypted"></param>
		/// <param name="connectTimeout"></param>
		/// <param name="persistSecurityInfo"></param>
		public ConnectProfile(int profileId, string profileName, string dataSource, string initialCatalog, string userID, string password, bool passwordCrypted, int connectTimeout, bool persistSecurityInfo)
		{
			this._ProfileID = profileId;
			this._ProfileName = profileName;
			this.DataSource = dataSource;
			this.InitialCatalog = initialCatalog;
			this.UserID = userID;
			this._SetPasswordExternal(password, passwordCrypted);
			this.PersistSecurityInfo = persistSecurityInfo;
			this.ConnectTimeout = connectTimeout;
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{" + this.ProfileID.ToString() + ": " + this.ProfileName + " (" + this.DataSource + "." + this.InitialCatalog + "}";
		}
		private int _ProfileID;
		private string _ProfileName;
		private string _DataSource;
		private string _InitialCatalog;
		private string _UserID;
		/// <summary>
		/// Heslo načtené z Noris.config, pokud je tam zakryptované (když je současně _PasswordCrypted = true).
		/// </summary>
		private string _PasswordNoris;
		/// <summary>
		/// Heslo registrované interně, když je _PasswordCrypted = false.
		/// </summary>
		private string _PasswordInternal;
        private byte[] _ConnectSessionId;
		private bool _PasswordCrypted;
		private string _Password1;
		private string _Password2;
		private int _ConnectTimeout;
		private bool _PersistSecurityInfo;
		#endregion
		#region SqlConnectionStringBuilder, ProfileConnString
		/// <summary>
		/// Connect string
		/// </summary>
		public string ProfileConnString { get { return ConnStrBuilder.ConnectionString; } set { ConnStrBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder(value); } }
		/// <summary>
		/// SqlConnectionStringBuilder
		/// </summary>
		public System.Data.SqlClient.SqlConnectionStringBuilder ConnStrBuilder
		{
			get
			{
				System.Data.SqlClient.SqlConnectionStringBuilder csb = new System.Data.SqlClient.SqlConnectionStringBuilder();
				csb.DataSource = this.DataSource;
				csb.InitialCatalog = this.InitialCatalog;
				string userID = this.UserID;
				if (!String.IsNullOrEmpty(userID))
				{
					csb.UserID = this.UserID;
					csb.Password = this.Password;             // Čtení hesla zohlední stav PasswordCrypted
				}
				else
				{
					csb.IntegratedSecurity = true;
				}

				csb.PersistSecurityInfo = this.PersistSecurityInfo;
				csb.ConnectTimeout = this.ConnectTimeout;
				return csb;
			}
			set
			{
				if (value != null)
				{
					this.DataSource = value.DataSource;
					this.InitialCatalog = value.InitialCatalog;
					this.UserID = value.UserID;
					this.PasswordCrypted = false;             // Heslo vkládáme čitelné
					this.Password = value.Password;           // Vložení čitelného hesla jej uloží jen zlehka zašifrovné do _PasswordInternal
					this.PersistSecurityInfo = value.PersistSecurityInfo;
					this.ConnectTimeout = value.ConnectTimeout;
				}
			}
		}
		#endregion
		#region PASSWORD
		/// <summary>
		/// Vrátí platné heslo, buď jej dekryptuje z Noris stringu, anebo z lokálního stringu. V paměti není trvale uloženo v čitelné formě.
		/// </summary>
		/// <returns></returns>
		private string _GetPassword()
		{
			string passw = Encoding.UTF8.GetString(this._ConnectSessionId);
			return (passw == null ? String.Empty : passw);
		}
		/// <summary>
		/// Vrátí dekryptované heslo metodou Noris
		/// </summary>
		/// <param name="passwordCrypt"></param>
		/// <returns></returns>
		private string _PasswordDecrypt(string passwordCrypt)
		{
			return Steward.NorisPasswordDecrypt(passwordCrypt);
		}
		/// <summary>
		/// Vrátí dekryptované heslo metodou Hex64
		/// </summary>
		/// <param name="passwordCrypt"></param>
		/// <returns></returns>
		private string _PasswordToText(string passwordCrypt)
		{
			if (passwordCrypt == null) return "";
			return Noris.Schedule.Support.Core.Crypt.Decrypt(passwordCrypt);
		}
		/// <summary>
		/// Uloží heslo do interních proměnných
		/// </summary>
		/// <param name="password"></param>
        /// <param name="passwordCrypted"></param>
		private void _SetPasswordExternal(string password, bool passwordCrypted)
		{
			this._PasswordCrypted = passwordCrypted;
            byte[] connectSessionId = null;
            if (passwordCrypted)
            {   // Heslo je zadáno kryptované
                this._PasswordNoris = password;
                connectSessionId = Encoding.UTF8.GetBytes(_PasswordDecrypt(password));
            }
            else
            {   // Heslo je zadáno čitelné
                string passvord = (String.IsNullOrEmpty(password) ? "" : password);
                this._PasswordInternal = Noris.Schedule.Support.Core.Crypt.EncryptToString(passvord);
                connectSessionId = Encoding.UTF8.GetBytes(passvord);
            }
            this._ConnectSessionId = connectSessionId;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// ID profilu
		/// </summary>
		public int ProfileID { get { return _ProfileID; } }
		/// <summary>
		/// Jméno profilu
		/// </summary>
		public string ProfileName { get { return _ProfileName; } set { _ProfileName = value; } }
		/// <summary>
		/// Gets or sets the name or network address of the instance of SQL Server to connect to.
		/// </summary>
		public string DataSource { get { return _DataSource; } set { _DataSource = value; } }
		/// <summary>
		/// Gets or sets the name of the database associated with the connection.
		/// </summary>
		public string InitialCatalog { get { return _InitialCatalog; } set { _InitialCatalog = value; } }
		/// <summary>
		/// Vrací jméno SQL serveru.databáze
		/// </summary>
		public string SourceDatabase { get { return _DataSource + "." + _InitialCatalog; } }
		/// <summary>
		/// Gets or sets the user ID to be used when connecting to SQL Server.
		/// </summary>
		public string UserID { get { return _UserID; } set { _UserID = value; } }
		/// <summary>
		/// Gets or sets the password for the SQL Server account.
		/// </summary>
		public string Password { get { return _GetPassword(); } set { _SetPasswordExternal(value, false); } }
		/// <summary>
		/// Příznak, že heslo na vstupu je kryptované přes NorisCrypt
		/// </summary>
		public bool PasswordCrypted { get { return _PasswordCrypted; } set { _PasswordCrypted = value; } }
		/// <summary>
		/// Gets or sets the connection timeout.
		/// The value of the System.Data.SqlClient.SqlConnectionStringBuilder.ConnectTimeout property, or 15 if no value has been supplied.
		/// </summary>
		public int ConnectTimeout { get { return _ConnectTimeout; } set { _ConnectTimeout = value; } }
		/// <summary>
		/// Heslo pro editaci 1
		/// </summary>
		public string Password1 { get { return _Password1; } set { _Password1 = value; } }
		/// <summary>
		/// Heslo pro editaci 2
		/// </summary>
		public string Password2 { get { return _Password2; } set { _Password2 = value; } }
		/// <summary>
		/// Gets or sets a Boolean value that indicates if security-sensitive information,
		/// such as the password, is not returned as part of the connection if the connection
		/// is open or has ever been in an open state.
		/// </summary>
		public bool PersistSecurityInfo { get { return _PersistSecurityInfo; } set { _PersistSecurityInfo = value; } }
		/// <summary>
		/// true, pokud je tento ConnectProfile zadán tak, aby mělo význam pokoušet se připojit k databázi.
		/// Tj. je vyplněný server, databáze. Pokud není vyplněno jméno UserID, pokusí se připojit přes Windows autentifikaci.
		/// </summary>
		public bool Correct { get { return this._IsCorrect(); } }
		private bool _IsCorrect()
		{
			return (!String.IsNullOrEmpty(this._DataSource) && !String.IsNullOrEmpty(this._InitialCatalog));
		}
		#endregion
		#region PUBLIC METODY
		/// <summary>
		/// Vrátí plný klon (DeepClone).
		/// Ten má shodné ID.
		/// </summary>
		/// <returns></returns>
		public ConnectProfile GetClone()
		{ return GetClone(false); }
		/// <summary>
		/// Vrátí plný klon (DeepClone)
		/// Ten má shodné ID.
		/// </summary>
		/// <returns></returns>
		public ConnectProfile GetClone(bool fillPassword12)
		{
			ConnectProfile clone = (ConnectProfile)this.MemberwiseClone();
			clone._Password1 = ((fillPassword12) ? clone.Password : String.Empty);
			clone._Password2 = clone._Password1;
			return clone;
		}
		/// <summary>
		/// Vrátí instanci s načtenými daty z WinReg
		/// </summary>
		/// <param name="profileId"></param>
		public static ConnectProfile LoadRecord(int profileId)
		{
			ConnectProfile result = new ConnectProfile(profileId);
			result.Load(profileId);
			if (result.ProfileName == "")
			{
				result.ProfileName = "Výchozí profil";
				result.Save();
			}
			return result;

		}
		/// <summary>
		/// Vytvoří a vrátí nový prázdný profil
		/// </summary>
		/// <returns></returns>
		public static ConnectProfile CreateNew()
		{
			int profileId = _GetNewProfileId();
			ConnectProfile result = new ConnectProfile(profileId);
			return result;
		}
		/// <summary>
		/// Vytvoří a vrátí nový profil, vzniklý jako kopie aktuálního profilu
		/// </summary>
		/// <returns></returns>
		public ConnectProfile CreateDuplicate()
		{
			int profileId = _GetNewProfileId();
			string name = this.ProfileName;
			if (!name.EndsWith("*")) name += " ";
			name += "*";
			ConnectProfile result = new ConnectProfile(profileId);
			result._ProfileName = name;
			result.ProfileConnString = this.ProfileConnString;
			return result;
		}
		/// <summary>
		/// Metoda najde a vrátí ID pro nový profil
		/// </summary>
		/// <returns></returns>
		private static int _GetNewProfileId()
		{
			Dictionary<int, string> dict = GetProfileList();
			int maxId = 0;
			foreach (int currId in dict.Keys)
				maxId = (currId > maxId ? currId : maxId);
			return maxId + 1;
		}
		/// <summary>
		/// Načte do sebe data daného profilu
		/// </summary>
		/// <param name="recordNumber"></param>
		public void Load(int recordNumber)
		{
			string profileNameReg = "Profile" + recordNumber.ToString("D4") + "Name";
			string profileNameCon = "Profile" + recordNumber.ToString("D4") + "Data";
			this._ProfileID = recordNumber;
            this._ProfileName = Steward.WinRegReadString(_PROFILE_FOLDER, profileNameReg, "");
            this.ProfileConnString = Steward.WinRegReadStringCrypt(_PROFILE_FOLDER, profileNameCon, "");
		}
		/// <summary>
		/// Uloží svoje instanční data do WinReg
		/// </summary>
		/// <param name="profileId"></param>
		public void Save()
		{
			string profileNameReg = "Profile" + this.ProfileID.ToString("D4") + "Name";
			string profileNameCon = "Profile" + this.ProfileID.ToString("D4") + "Data";
            Steward.WinRegWriteString(_PROFILE_FOLDER, profileNameReg, this.ProfileName);
            Steward.WinRegWriteStringCrypt(_PROFILE_FOLDER, profileNameCon, this.ProfileConnString);
		}
		/// <summary>
		/// Načte a vrátí soupis profilů z registru
		/// </summary>
		/// <returns></returns>
		public static Dictionary<int, string> GetProfileList()
		{
			Dictionary<int, string> result = new Dictionary<int, string>();
			List<string> profileNameList = Steward.WinRegGetValueNames(_PROFILE_FOLDER);
			System.Text.RegularExpressions.Regex pattern = new System.Text.RegularExpressions.Regex("Profile[0-9]{4}Name");
			foreach (string profileNameReg in profileNameList)
			{
				if (pattern.Match(profileNameReg).Success)
				{
                    string name = Steward.WinRegReadString(_PROFILE_FOLDER, profileNameReg, "");
					if (!String.IsNullOrEmpty(name))
					{
						int profileID = Convert.ToInt32(profileNameReg.Substring(7, 4));
						if (!result.ContainsKey(profileID))
							result.Add(profileID, name);
					}
				}
			}
			return result;
		}
		#endregion
		#region CONSTANTS
		private const string _REGFOLDER = "Config";
		private const string _PROFILE_FOLDER = "Config" + "\\Profiles";
		#endregion
		#region IRecord Members
		/// <summary>
		/// Vynuluje obsah tohoto objektu, přidělí mu nový klíč
		/// </summary>
		public void Reset()
		{
			this._ProfileID = _GetNewProfileId();
			this.ConnStrBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder();
			this._ProfileName = String.Empty;
			this._Password1 = String.Empty;
			this._Password2 = String.Empty;
		}
		/// <summary>
		/// Vrátí nový objekt, který obsahuje data z objektu this. Má nový klíč.
		/// </summary>
		/// <param name="createTemporaryId"></param>
		/// <returns></returns>
		public object CreateNewDuplicate(bool createTemporaryId)
		{
			return this.CreateDuplicate();
		}
		public int ClassNumber
		{
			get { return 0; }
		}
		public int RecordNumber
		{
			get { return this._ProfileID; }
		}
		#endregion
	}
	#endregion
}
